import { Component, instantiate, Node, NodePool, Prefab, SpriteFrame, Tween } from 'cc';
import { DEBUG, PREVIEW } from 'cc/env';

// FIXME 暂不做通用性拓展
declare type prefabObj = { [key: string]: Prefab };
declare type poolObj = { [key: string]: NodePool };
declare type spriteObj = { [key: string]: SpriteFrame };

/**
 * 全局资源管理
 */
export default class AssetModule {

    // 单例
    private static _ins: AssetModule = null;
    public static getIns(): AssetModule {
        if (!AssetModule._ins) {
            AssetModule._ins = new AssetModule();
        }
        return AssetModule._ins;
    }

    private spData: spriteObj = {};
    private poolData: poolObj = {};
    private prefabData: prefabObj = {};
    private constructor() { }

    /**
     * 修正资源名称
     * @param name 
     * @param asset 
     * @returns 
     */
    private static fixName(name: string | number | undefined, asset?: SpriteFrame | Prefab): string {
        if (typeof name === "number")
            name = name.toString();
        if (!name && !!asset && asset.isValid)
            name = asset.name;
        if (name != undefined)
            name = name.trim();
        return name;
    }


    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * 存入精灵帧资源
     * @param sp [SpriteFrame] 精灵帧资源
     * @param name [string] 资源别名（不传别名则使用资源名）
     */
    static setSpriteFrame(sp: SpriteFrame, name?: string | number) {
        if (!sp || !sp.isValid)
            return;
        name = AssetModule.fixName(name, sp);
        const data = AssetModule.getIns().spData;
        if (data[name] != undefined)
            DEBUG && console.debug("[AssetModule] 覆盖存入 SpriteFrame 资源, 名：", name);
        data[name] = sp;
    }

    /**
     * 存入精灵帧资源
     * @param list [SpriteFrame[]] 精灵帧资源数组
     */
    static setSpriteFrameList(list: SpriteFrame[]) {
        if (!list) return;
        list.forEach(sp => AssetModule.setSpriteFrame(sp));
    }

    /**
     * 获取精灵帧资源
     * @param name [string | number] 资源别名（number类型会自转化为string类型处理）
     */
    static getSpriteFrame(name: string | number): SpriteFrame {
        name = AssetModule.fixName(name);
        if (!name) return null;
        const data = AssetModule.getIns().spData;
        return data[name];
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * 存入预制资源
     * @param pb [Prefab] 预制资源
     * @param name [string] 资源别名（不传别名则使用资源名）
     */
    static setPrefab(pb: Prefab, name?: string | number) {
        if (!pb || !pb.isValid)
            return;
        name = AssetModule.fixName(name, pb);
        const data = AssetModule.getIns().prefabData;
        if (data[name] != undefined && data[name] != null)
            DEBUG && console.debug("[AssetModule] 覆盖存入 Prefab 资源, 名：", name);
        data[name] = pb;
    }

    /** 移除指定资源，释放内存 */
    static delPrefab(prefab: string | Prefab) {
        let name: string = typeof prefab == "string" ? prefab : prefab.name;
        if (!name || !name.trim()) return;
        name = name.trim();
        const data = AssetModule.getIns().prefabData;
        if (data[name] == undefined || data[name] == null)
            return;
        delete data[name];
        data[name] = null;
    }

    /**
     * 存入预制资源
     * @param list [Prefab[]] 预制资源数组
     */
    static setPrefabList(list: Prefab[]) {
        if (!list) return;
        list.forEach(pb => AssetModule.setPrefab(pb));
    }

    /** 移除指定资源列表资源，释放内存 */
    static delPrefabList(list: Prefab[]) {
        if (!list) return;
        list.forEach(pb => AssetModule.delPrefab(pb));
    }

    /**
     * 获取预制资源
     * @param name [string | number] 资源别名（number类型会自转化为string类型处理）
     */
    static getPrefab(name: string | number): Prefab {
        name = AssetModule.fixName(name);
        if (!name) return null;
        const data = AssetModule.getIns().prefabData;
        return data[name];
    }

    /**
     * 获取预制资源实例节点
     * @param name [string | number] 资源别名（number类型会自转化为string类型处理）
     * @returns 
     */
    static getPrefabNode(name: string | number): Node {
        const pb = AssetModule.getPrefab(name);
        if (!pb) return null;
        let node = AssetModule.getIns().getPoolNode(name);
        if (!node) {
            node = instantiate(pb);
            node.attr({ "poolName": name });
            AssetModule.getIns().poolData[name] = new NodePool();
        }
        return node;
    }

    // 获取对象池指定节点
    private getPoolNode(name: string | number): Node {
        name = AssetModule.fixName(name);
        if (!name || !this.poolData[name])
            return null;
        return this.poolData[name].get();
    }

    /**
     * 回收预制资源实例节点
     * （非预制体实例化的节点传入将被销毁，请谨慎使用）
     * @param node 
     */
    static putPoolNode(node: Node) {
        if (!node || !node.isValid) return;
        // stop actions
        Tween.stopAllByTarget(node);
        const name: string = node["poolName"] as string;
        if (!!name && !!AssetModule.getIns().poolData[name]) {
            AssetModule.getIns().poolData[name].put(node);
        }
        else {
            node.destroy();
            DEBUG && console.debug("[AssetModule] 无法回收非预制体节点，销毁节点名：", node.name);
        }
    }

    /**
     * 回收预制资源实例节点列表
     * @description(备注：类型不匹配的不做任何处理)
     * @description(备注：回收后会修改数组列表)
     * @param list 节点列表
     * @param poolName 指定的回收池名（不传则回收所有类型可配置项目）
     */
    static putPoolNodeList<T extends Component>(list: (Node | T)[], poolName?: string) {
        const len = list ? list.length : 0;
        if (!len) return;
        for (let i = len - 1; i >= 0; i--) {
            const item: Node | T = list[i];
            if (!item || !item.isValid)
                continue;

            const node: Node = Node.isNode(item) ? item : item.node;
            if ((poolName != undefined || poolName != null) && node["poolName"] as string != poolName) {
                PREVIEW && console.log(`[AssetModule] node:${node.name} 不是本次回收目标:${poolName}，跳过处理！`);
                continue;
            }
            this.putPoolNode(node);
            list.splice(i, 1);
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

}
